/******************************************************************************
 * This file is part of libemb.
 *
 * libemb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libemb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with libemb.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Project: Embedme
 * Author : FergusZeng
 * Email  : cblock@126.com
 * git	  : https://git.oschina.net/cblock/embedme
 * Copyright 2014~2017 @ ShenZhen ,China
*******************************************************************************/
#include "BaseType.h"
#include "WavAudio.h"
#include "Tracer.h"
#include "File.h"
#include "asoundlib.h"
#include <stdlib.h>

#define ID_RIFF     0x46464952
#define ID_WAVE     0x45564157
#define ID_FMT      0x20746d66
#define ID_DATA     0x61746164
#define FORMAT_PCM  1

#define WAV_HEADER_SIZE     (12)
#define CHUNK_HEADER_SIZE   (8)
#define CHUNK_FORMAT_SIZE   (16)
#define DATA_HEADER_SIZE    (8)
typedef struct{
    /* wav header */
    uint32 m_riffId;
    uint32 m_riffSize;
    uint32 m_riffFormat;
    /* chunk header */
    uint32 m_formatId;
    uint32 m_formatSize;
    /* chunk format */
    uint16 m_audioFormat;
    uint16 m_numChannels;
    uint32 m_sampleRate;
    uint32 m_byteRate;
    uint16 m_blockAlign;
    uint16 m_bitsPerSample;
    /* data header */
    uint32 m_dataId;
    uint32 m_dataSize;
}WavHeader_S;

/* PcmDevice仅供内部使用,所以不暴露头文件 */
class PcmDevice{
public:
    typedef enum{
        PCM_PLAYER=0,
        PCM_RECORDER
    }PCM_TYPE_E;
public:
    PcmDevice(int pcmType,int device,int card,int channels);
    ~PcmDevice();
    bool open(int rate, int bits, int periodSize,int periodCount);
    bool close();
    int playFrames(char *frameBuf, int frameNum);
    int recordFrames(char *frameBuf, int frameNum);
    int getFrameSize();
private:
    struct pcm* m_pcm;
    int m_pcmType;
    int m_device;
    int m_card;
    int m_channels;
    int m_frameSize;
};

PcmDevice::PcmDevice(int pcmType,int device,int card,int channels)
{
    m_pcm = NULL;
    m_pcmType = pcmType;
    m_device = device;
    m_card = card;
    m_channels = channels;
    m_frameSize = 0;
}

PcmDevice::~PcmDevice()
{
    this->close();
}

bool PcmDevice::open(int rate, int bits, int periodSize,int periodCount)
{
    if (m_pcm!=NULL) 
    {
        TRACE_ERR_CLASS("alsa device is already opened,device:%d,card:%d\n", m_device,m_card);
        return false;
    }
    if (m_device < 0 || m_card < 0 || m_channels < 0 ||
        rate < 0 || bits < 0 || periodSize<0 || periodCount <0) 
    {
        TRACE_ERR_CLASS("invalid params: device(%d),card(%d),channels(%d),"
                        "rate(%d),bits(%d),periodSize(%d),periodCount(%d).\n",
                        m_device, m_card, m_channels, rate, bits, periodSize, periodCount);
        return false;
    }
    struct pcm_config config;
    config.channels = m_channels;
    config.rate = rate;
    config.period_size = periodSize;
    config.period_count = periodCount;
    if (bits == 32)
    {
        config.format = PCM_FORMAT_S32_LE;
    }
    else if (bits == 16)
    {
        config.format = PCM_FORMAT_S16_LE;
    }
    config.start_threshold = 0;
    config.stop_threshold = 0;
    config.silence_threshold = 0;

    switch (m_pcmType) {
    case PCM_PLAYER:
        m_pcm = pcm_open(m_card, m_device, PCM_OUT, &config);
        break;
    case PCM_RECORDER:
        m_pcm = pcm_open(m_card, m_device, PCM_IN, &config);
        break;
    default:
        TRACE_ERR_CLASS("Unsupport pcm type:%d.\n",m_pcmType);
        return false;
    }

    if (NULL==m_pcm || !pcm_is_ready(m_pcm)) 
    {
        TRACE_ERR_CLASS("Unable to open PCM device %d (%s)\n",m_device, pcm_get_error(m_pcm));
        return false;
    }

    switch (m_pcmType) {
    case PCM_PLAYER:
        m_frameSize = pcm_frames_to_bytes(m_pcm, pcm_get_buffer_size(m_pcm));
        break;
    case PCM_RECORDER:
        m_frameSize = pcm_get_buffer_size(m_pcm); 
        break;
    default:
        TRACE_ERR_CLASS("Unsupport pcm type:%d.\n",m_pcmType);
        return false;
    }
    return true;
}

bool PcmDevice::close()
{
    if (m_pcm!=NULL) 
    {
        pcm_close(m_pcm);
        m_pcm=NULL;
    }
    return true;
}

int PcmDevice::playFrames(char *frameBuf, int frameNum)
{
    if (frameNum <= 0 || m_frameSize<=0) 
    {
        TRACE_ERR_CLASS("Invalid params,frameNum:%d,frameSize:%d.\n",frameNum,m_frameSize);
        return STATUS_ERROR;
    }
    uint32 i;
    for (i=0; i<frameNum; i++) 
    {
        if (0!=pcm_write(m_pcm, frameBuf+(i*m_frameSize), m_frameSize)) 
        {
            TRACE_ERR_CLASS("playing frames error:%s.\n",pcm_get_error(m_pcm));
            break;
         }
    }
    return (int)i;
}
int PcmDevice::recordFrames(char *frameBuf, int frameNum)
{
    if (frameNum <= 0 || m_frameSize<=0) 
    {
        TRACE_ERR_CLASS("Invalid params,frameNum:%d,frameSize:%d.\n",frameNum,m_frameSize);
        return STATUS_ERROR;
    }
    uint32 i;
    for (i=0; i<frameNum; i++) 
    {
        if (0!=pcm_read(m_pcm, frameBuf+(i*m_frameSize), m_frameSize)) 
        {
            TRACE_ERR_CLASS("recording frames error:%s.\n",pcm_get_error(m_pcm));
            break;
         }
    }
    return (int)i;
}

int PcmDevice::getFrameSize()
{
    return m_frameSize;
}


WavAudio::WavAudio()
{
    m_startPlaying = false;
    m_startRecording = false; 
}

WavAudio::~WavAudio()
{
}

/**
 *  \brief  开始播放wav格式音频文件
 *  \param  wavFileName wav文件名
 *  \param  device 设备号(0~255,默认为0)
 *  \param  card 声卡号(0~255,默认为0)
 *  \param  periodSize (缓冲区大小默认为1024)
 *  \param  periodCount (缓冲区数量默认为4)
 *  \return 成功返回true,失败返回false
 *  \note   none
 */
bool WavAudio::startPlay(std::string wavFileName,int device, int card, int periodSize, int periodCount)
{
    if (wavFileName.empty()) 
    {
        TRACE_ERR_CLASS("wavFileName empty.\n");
        return false;
    }
    
    if (m_startPlaying) 
    {
        TRACE_ERR_CLASS("playing is allready started.\n");
        return false;
    }

    WavHeader_S wavHeader;
    memset(&wavHeader,0,sizeof(wavHeader));
    File wavFile;
    if(!wavFile.open(wavFileName.c_str(), IO_MODE_RD_ONLY))
    {
        TRACE_ERR_CLASS("open wav file error:%s.\n",wavFileName.c_str());
        return false;
    }

    if ((WAV_HEADER_SIZE+CHUNK_HEADER_SIZE) != wavFile.readData((char *)&wavHeader, (WAV_HEADER_SIZE+CHUNK_HEADER_SIZE))) 
    {
        TRACE_ERR_CLASS("read wav header error:%s.\n",wavFileName.c_str());
        return false;
    }
    if ((wavHeader.m_riffId != ID_RIFF) || 
        (wavHeader.m_riffFormat != ID_WAVE) ||
        (wavHeader.m_formatId != ID_FMT)) 
    {
        TRACE_ERR_CLASS("Error wav file:%s,riffID:0x%x,riffFormat:0x%x,formatId:0x%x.\n",
        wavFileName.c_str(),wavHeader.m_riffId,wavHeader.m_riffFormat,wavHeader.m_formatId);
        wavFile.close();
        return false;
    }

    /* 读chunk format数据 */
    wavFile.readData(((char *)&wavHeader) +WAV_HEADER_SIZE+CHUNK_HEADER_SIZE,wavHeader.m_formatSize );

    /* 读data header数据 */
    wavFile.readData(((char *)&wavHeader) + WAV_HEADER_SIZE + CHUNK_HEADER_SIZE + wavHeader.m_formatSize, 8); 
    if (wavHeader.m_dataId!=ID_DATA) 
    {
        TRACE_ERR_CLASS("Error wav file:%s,dataId:0x%x.\n",wavFileName.c_str(),wavHeader.m_dataId);
        wavFile.close();
        return false;
    }

    /* 创建音频设备 */
    PcmDevice pcmDevice(PcmDevice::PCM_PLAYER, device, card, wavHeader.m_numChannels);
    if (!pcmDevice.open(wavHeader.m_sampleRate, wavHeader.m_bitsPerSample, periodSize,periodCount))
    {
        TRACE_ERR_CLASS("open PcmDevice failed.\n");
        wavFile.close();
        return false;
    }
    
    /* 分配缓存 */
    int frameSize = pcmDevice.getFrameSize();
    char* frameBuffer = (char*)malloc(frameSize);
    if (NULL==frameBuffer) 
    {
        TRACE_ERR_CLASS("Unable to allocate %d bytes\n", frameSize);
        free(frameBuffer);
        wavFile.close();
        return false;
    }
    m_startPlaying=true;
    while (m_startPlaying) 
    {
        if(frameSize!=wavFile.readData(frameBuffer,frameSize))
        {
            break;
        }
        pcmDevice.playFrames(frameBuffer,1);
    }
    free(frameBuffer);
    wavFile.close();
    pcmDevice.close();
    return true;
}

/**
 *  \brief  停止播放wav格式音频文件
 *  \param  none
 *  \return 成功返回true,失败返回false
 *  \note   none
 */
bool WavAudio::stopPlay()
{
    m_startPlaying = false;
    return true;
}

/**
 *  \brief  开始录制wav格式音频文件
 *  \param  wavFileName wav文件名
 *  \param  rates 采样率
 *  \param  bits 采样位数
 *  \param  device 设备号(0~255,默认为0)
 *  \param  card 声卡号(0~255,默认为0)
 *  \param  channel 通道数(默认为2)
 *  \param  periodSize (缓冲区大小默认为1024)
 *  \param  periodCount (缓冲区数量默认为4)
 *  \return 成功返回true,失败返回false
 *  \note   none
 */
bool WavAudio::startRecord(std::string wavFileName,int rates,int bits,int device, int card, int channels,
                              int periodSize, int periodCount)
{
    if (wavFileName.empty()) 
    {
        TRACE_ERR_CLASS("wavFileName empty.\n");
        return false;
    }
    if (m_startRecording) 
    {
        TRACE_ERR_CLASS("recording is allready started.\n");
        return false;
    }
    
    /* 初始化文件头 */
    WavHeader_S wavHeader;
    memset(&wavHeader,0,sizeof(wavHeader));
    wavHeader.m_riffId = ID_RIFF;
    wavHeader.m_riffSize = 0;
    wavHeader.m_riffFormat = ID_WAVE; 
    wavHeader.m_formatId = ID_FMT; 
    wavHeader.m_formatSize = 16; 
    wavHeader.m_audioFormat = FORMAT_PCM;
    wavHeader.m_numChannels = channels;
    wavHeader.m_sampleRate = rates;
    wavHeader.m_byteRate = (bits / 8) * channels * rates;
    wavHeader.m_blockAlign = channels * (bits / 8);
    wavHeader.m_bitsPerSample = bits;
    wavHeader.m_dataId = ID_DATA;

    /* 创建wav文件 */
    File wavFile;
    if (!wavFile.open(wavFileName.c_str(), IO_MODE_REWR_ORNEW)) 
    {
        TRACE_ERR_CLASS("open wav file error:%s.\n",wavFileName.c_str());
        return false;
    }
    /* 跳过文件头 */
    wavFile.setPos(sizeof(WavHeader_S));

    /* 打开录音设备 */
    PcmDevice pcmDevice(PcmDevice::PCM_RECORDER, device, card, channels);
    if (!pcmDevice.open(wavHeader.m_sampleRate, wavHeader.m_bitsPerSample, periodSize,periodCount))
    {
        TRACE_ERR_CLASS("open PcmDevice failed.\n");
        wavFile.close();
        return false;
    }
    
    /* 分配缓存 */
    int frameSize = pcmDevice.getFrameSize();
    char* frameBuffer = (char*)malloc(frameSize);
    if (NULL==frameBuffer) {
        TRACE_ERR_CLASS("Unable to allocate %d bytes\n", frameSize);
        free(frameBuffer);
        wavFile.close();
        pcmDevice.close();
        return false;
    }

    /* 开始录音 */
    uint32 bytesRecord=0;
    m_startRecording = true; 
    while (m_startRecording) 
    {
        int frameNum=1;
        if(frameNum!=pcmDevice.recordFrames(frameBuffer,frameNum))
        {
            break;
        }
        
        if(frameSize!=wavFile.writeData(frameBuffer,frameSize))
        {
            break;
        }
        bytesRecord += frameSize;
    }
    free(frameBuffer);

    /* 补充头信息 */
    uint32 frames = bytesRecord/((wavHeader.m_bitsPerSample/8)*channels);
    wavHeader.m_dataSize = frames * wavHeader.m_blockAlign;
    wavHeader.m_riffSize = wavHeader.m_dataSize + sizeof(WavHeader_S) - 8; 
    wavFile.setPos(0);
    wavFile.writeData((const char*)&wavHeader, sizeof(WavHeader_S));
    wavFile.close();
    
    /* 关闭设备 */
    pcmDevice.close();
    return true;
}

/**
 *  \brief  停止录制wav格式音频文件
 *  \param  none
 *  \return 成功返回true,失败返回false
 *  \note   none
 */
bool WavAudio::stopRecord()
{
    m_startRecording = false;
    return true;
}
